提醒:由於看到這系列鐵人訂閱人數漸漸變多,標記一下這些內容是在「非常萌新時期」所寫,更多技術內容請參考我的 Github,歡迎跟我一起討論 ^ ^
今天接著來看看 Express 是如何實作 Session。在前面說過 Express 是以許多 middleware 來運作,而處理 Session 的 middleware 是「express-session」。
這個部分找到一篇不錯文章介紹了 express-session 實作 Session 的方式。
const express = require('express')
const session = require('express-session')
const app = express()
// 會以一個 object 當作參數帶入,在其中設定各種屬性。
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
// 之後即可使用「req.session.key」在 Session 中存取資料
各種屬性的說明:
文章中講解了 express-session 實作 Session 的三個重要部分:sessionID 如何產生、sessionID 儲存方式、session 資訊儲存方式。
// 可以自訂 產生ID的函式 寫在 genid 屬性(寫在上述的 object),或是用預設的
var generateId = opts.genid || generateSessionId
if (typeof generateId !== 'function') {
throw new TypeError('genid option must be a function');
}
// 生成一個 Session 時,同時產生了一個ID
store.generate = function(req){
req.sessionID = generateId(req);
req.session = new Session(req);
req.session.cookie = new Cookie(cookieOptions);
if (cookieOptions.secure === 'auto') {
req.session.cookie.secure = issecure(req, trustProxy);
}};
// 預設產生ID的方式:以 uid-safe 這個library來生成隨機ID
function generateSessionId(sess) {return uid(24);}
將 sessionID 會以以下形式儲存在 cookie
key: connect.sid(或自訂)
value: s:{sessionID}.{hmac-sha256(sessionID, secret)}
var cookie = require('cookie')
var signature = require('cookie-signature')
/* cookie 中,存放ID的key。可以自訂 寫在「name」或「key」屬性,或是直接使用預設的connect.sid。*/
var name = opts.name || opts.key || 'connect.sid'
var secret = opts.secret
if (secret && !Array.isArray(secret)) {
secret = [secret];
}
/* 加入「Set-Cookie」 Header。當 secret 為 Array 時,只有第一項會被放進 Cookie。*/
onHeaders(res, function(){
setcookie(res, name, req.sessionID, secrets[0], req.session.cookie.data);
});
/* 這裡產生的ID形式,使用了 'cookie-signature' 的 sign 功能,在其後面加了一段鑑別碼,使其無法被串改。 */
function setcookie(res, name, val, secret, options) {
var signed = 's:' + signature.sign(val, secret);
var data = cookie.serialize(name, signed, options);
debug('set-cookie %s', data);
var prev = res.getHeader('Set-Cookie') || []
var header = Array.isArray(prev) ? prev.concat(data) : [prev, data];
res.setHeader('Set-Cookie', header)
}
若要使用在產品上,則要另外找地方儲存,例如數據庫(如 MongoDB)、緩存(如 Redis)。
// 可以自訂儲存的方式 寫在 store 屬性,預設為存在記憶體。
var store = opts.store || new MemoryStore()
// 當直接將預設使用在產品時,會提醒你此規格不適合使用在產品。
if (env === 'production' && store instanceof MemoryStore) {
console.warn(warning);}
/* 預設的方式:在記憶體中建立一個object變數,然後以「key:ID、value:session內容」來儲存。 */
function MemoryStore() {
Store.call(this)
this.sessions = Object.create(null)
}
MemoryStore.prototype.get = function get(sessionId, callback) {
defer(callback, null, getSession.call(this, sessionId))
}
MemoryStore.prototype.set = function set(sessionId, session, callback) {
this.sessions[sessionId] = JSON.stringify(session)
callback && defer(callback)
}
function getSession(sessionId) {
var sess = this.sessions[sessionId]
if (!sess) {return}
sess = JSON.parse(sess)
return sess
}